/*
 *  Copyright 2012 Ricardo Engel
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.mookt.quantity;

import java.math.RoundingMode;
import java.util.Arrays;
import java.util.Iterator;

import com.google.common.base.Preconditions;

/**
 * The class {@link MathQuantity} contains methods for performing basic numeric
 * operations such as the elementary exponential, logarithm, square root.
 * 
 * @author Ricardo Engel
 * 
 */
public class MathQuantity {

    private static final String NULL_QUANTITY = "Quantity cannot be null";

    /**
     * This class should be non-instantiable.
     */
    private MathQuantity() {
    }

    /**
     * Returns a {@link Quantifiable} instance with a positive amount of
     * {@code q}.
     * 
     * <p>
     * If {@code q} is positive or zero, a {@code Quantifiable} of same amount
     * and unit is returned. If {@code q} is negative, a {@code Quantifiable} of
     * opposite value but same unit is returned.
     * </p>
     * 
     * @param q a {@code Quantifiable}, not-null.
     * @return the absolute amount of the {@code Quantifiable} argument.
     * @throws NullPointerException if {@code q} is {@code null}.
     */
    public static <Q extends Quantifiable<Q>> Q abs(final Q q) {
        Preconditions.checkNotNull(NULL_QUANTITY, q);
        return q.isNegative() ? q.opposite() : q;
    }

    /**
     * Returns a {@code Quantifiable} whose amount is the smallest integral
     * number not less than the amount of the argument.
     * 
     * <p>
     * Please note that {@code ceil(x) = -floor(-x)} for any given {@code x}.
     * </p>
     * 
     * @param q a {@code Quantifiable}, not-null.
     * @return a {@code Quantifiable} whose amount is the smallest integral
     *         number not less than the amount of the argument.
     * @throws NullPointerException if {@code q} is {@code null}.
     */
    public static <Q> Q ceil(final Quantifiable<Q> q) {
        Preconditions.checkNotNull(NULL_QUANTITY, q);
        return q.round(0, RoundingMode.CEILING);
    }

    public static <Q extends Quantifiable<Q>> Q exp(final Q q) {
        return null;
    }

    /**
     * Returns a {@code Quantifiable} whose amount is the largest integral
     * number not greater than the amount of the argument.
     * 
     * <p>
     * Please note that {@code floor(x) = -ceil(-x)} for any given {@code x}.
     * </p>
     * 
     * @param q a {@code Quantifiable}, not-null.
     * @return a {@code Quantifiable} whose amount is the largest integral
     *         number not greater than the amount of the argument.
     * @throws NullPointerException if {@code q} is {@code null}.
     */
    public static <Q> Q floor(final Quantifiable<Q> q) {
        Preconditions.checkNotNull(NULL_QUANTITY, q);
        return q.round(0, RoundingMode.FLOOR);
    }

    public static <Q extends Quantifiable<Q>> Q log(final Q q) {
        return null;
    }

    public static <Q extends Quantifiable<Q>> Q log10(final Q q) {
        return null;
    }

    public static <Q extends Quantifiable<Q>> Q log2(final Q q) {
        return null;
    }

    /**
     * Returns the greater of two arguments.
     * 
     * <p>
     * Returns the greater of {@code q1} and {@code q2}. If
     * {@code q1.equals(q2)}, then {@code q1} is returned.
     * </p>
     * 
     * @param q1 a {@code Quantifiable}, not-null.
     * @param q2 a {@code Quantifiable}, not-null.
     * @return the greater of {@code q1} and {@code q2}.
     * @throws NullPointerException if either {@code q1} or {@code q1} is
     *         {@code null}.
     */
    public static <Q extends Quantifiable<Q>> Q max(final Q q1, final Q q2) {
        Preconditions.checkNotNull(q1, NULL_QUANTITY);
        Preconditions.checkNotNull(q2, NULL_QUANTITY);
        return q2.isSmallerThan(q1) ? q1 : q2;
    }

    /**
     * Returns the lesser of two arguments.
     * 
     * <p>
     * Returns the lesser of {@code q1} and {@code q2}. If {@code q1.equals(q2)}
     * , then {@code q1} is returned.
     * </p>
     * 
     * @param q1 a {@code Quantifiable}, not-null.
     * @param q2 a {@code Quantifiable}, not-null.
     * @return the lesser of {@code q1} and {@code q2}.
     * @throws NullPointerException if either {@code q1} or {@code q1} is
     *         {@code null}.
     */
    public static <Q extends Quantifiable<Q>> Q min(final Q q1, final Q q2) {
        Preconditions.checkNotNull(q1, NULL_QUANTITY);
        Preconditions.checkNotNull(q2, NULL_QUANTITY);
        return q2.isGreaterThan(q1) ? q1 : q2;
    }

    /**
     * Obtains a {@code Quantifiable} instance as the total value of an array.
     * 
     * <p>
     * The array must contain at least one element and all amounts must be in
     * the same unit.
     * </p>
     * 
     * @param qs the list of {@code Quantifiable} to total, not empty, no null
     *        elements, not null.
     * @return the total, never null.
     * @throws IllegalArgumentException if the array is empty.
     * @throws NullPointerException if {@code qs} is null or contain a null
     *         element.
     * @throws UnitMismatchException if the units differ.
     */
    public static <Q extends Quantifiable<Q>> Q total(final Q... qs) {
        return total(Arrays.asList(qs));
    }

    /**
     * Obtains a {@code Quantifiable} instance as the total value of an
     * {@code Iterable}.
     * 
     * <p>
     * The {@code Iterable} must contain at least one element and all amounts
     * must be in the same unit.
     * </p>
     * 
     * @param qs the list of {@code Quantifiable} to total, not empty, no null
     *        elements, not null.
     * @return the total, never null.
     * @throws IllegalArgumentException if the {@code Iterable} is empty.
     * @throws NullPointerException if {@code qs} is null or contains a null
     *         element.
     * @throws UnitMismatchException if the units differ.
     */
    public static <Q extends Quantifiable<Q>> Q total(final Iterable<Q> qs) {
        Preconditions.checkNotNull(qs, NULL_QUANTITY);
        final Iterator<Q> quantityIter = qs.iterator();
        Preconditions.checkArgument(quantityIter.hasNext(),
                "Iterable cannot be empty");
        Q total = null;
        while (quantityIter.hasNext()) {
            final Q q = quantityIter.next();
            Preconditions.checkNotNull(q, NULL_QUANTITY);
            total = (total == null) ? q : q.plus(total);
        }
        return total;
    }

}